using System;
using System.ComponentModel;
using System.Runtime.Serialization;

using SharpGL.Media3D;

namespace SharpGL.SceneGraph.Cameras
{

	/// <summary>
	/// The camera class is a base for a set of derived classes for manipulating the
	/// projection matrix.
	/// </summary>
	[DataContract()]
	public abstract class Camera : INotifyPropertyChanged
    {
        #region Private Fields

        /// <summary>
        /// The name.
        /// </summary>
        [DataMember()]
        private string name = "Camera";

        /// <summary>
        /// The camera position.
        /// </summary>
        [DataMember()]
        private Point3D position = new Point3D(0, 0, 0);

        /// <summary>
        /// The screen aspect ratio.
        /// </summary>
        [DataMember()]
        private double aspectRatio = 1.0f;

        /// <summary>
        /// Every time a camera is used to project, the projection matrix calculated 
        /// and stored here.
        /// </summary>
        private Matrix projectionMatrix = new Matrix(4, 4);

        #endregion Private Fields

        #region Protected Methods
        
        /// <summary>
        /// Call this function to do the PropertyChanged event.
        /// </summary>
        protected void NotifyPropertyChanged(string propertyName)
        {
            var handler = PropertyChanged;
            if (handler != null)
                handler(this, new PropertyChangedEventArgs(propertyName));
        }

        #endregion Protected Methods
        
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Camera"/> class.
        /// </summary>
		public Camera()
		{
		}

        #endregion  Constructors

        #region Public Methods

        /// <summary>
		///	This function projects through the camera, to OpenGL, ie. it 
		///	creates a projection matrix.
		/// </summary>
		public virtual void Project(OpenGL gl)
		{
			//	Load the projection identity matrix.
            gl.MatrixMode(OpenGL.GL_PROJECTION);
			gl.LoadIdentity();

			//	Perform the projection.
		    TransformProjectionMatrix(gl);

			//	Get the matrix.
			float[] matrix = new float[16];
            gl.GetFloat(OpenGL.GL_PROJECTION_MATRIX, matrix);
			for(int i=0; i<4; i++)
				for(int j=0; j<4; j++)
					projectionMatrix[i,j] = matrix[(i*4) + j];
				
			//	Back to the modelview matrix.
            gl.MatrixMode(OpenGL.GL_MODELVIEW);
		}

		/// <summary>
		/// This function is for when you simply want to call only the functions that
		/// would transform the projection matrix. Warning, it won't load the identity
		/// first, and it won't set the current matrix to projection, it's really for
		/// people who need to use it for their own projection functions (e.g Picking
		/// uses it to create a composite 'Pick' projection).
		/// </summary>
        public abstract void TransformProjectionMatrix(OpenGL gl);

        #endregion Public Methods

        #region Public Events

        [Description("Called when properties has been changed."), Category("SharpGL")]
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion Public Events

        #region Public Properties

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        /// <value>
        /// The name.
        /// </value>
        [Description("The camera name."), Category("Camera")]
        public virtual string Name
        {
            get { return name; }
            set
            {
                if (name != value)
                {
                    name = value;

                    NotifyPropertyChanged("Name");
                }
            }
        }

        /// <summary>
        /// Gets or sets the position.
        /// </summary>
        /// <value>
        /// The position.
        /// </value>
        [Description("The position of the camera"), Category("Camera")]
        public Point3D Position
        {
            get { return position; }
            set
            {
                if (position != value)
                {
                    position = value;

                    NotifyPropertyChanged("Position");
                }
            }
        }

        /// <summary>
        /// Gets or sets the aspect.
        /// </summary>
        /// <value>
        /// The aspect.
        /// </value>
        [Description("Screen Aspect Ratio"), Category("Camera")]
        public double AspectRatio
        {
            get { return aspectRatio; }
            set
            {
                if (aspectRatio != value)
                {
                    aspectRatio = value;

                    NotifyPropertyChanged("AspectRatio");
                }
            }
        }

        #endregion Public Properties
    }
}
